package dev.ece.core.shader;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;

import dev.ece.core.clearup.ClearUpGLRubbish;
import dev.ece.core.clearup.ClearUpGLRubbishManager;
import dev.ece.util.tools.Constants;
/**
 * shader基础类
 * @author 飘渺青衣
 * <b>E-mail：</b>3375758@qq.com
 */
public abstract class Shader implements ClearUpGLRubbish {
	
	protected final Logger logger = LogManager.getLogger(this.getClass());
	
	private final static List<Shader> USING_SHADERS = new ArrayList<Shader>();
	
	private int program;
	
	private int loacation;
	
	private int vertexShaderHandler;
	
	private int pixelShaderHandler;
	
	private MatrixHandler matrixHandler;
	
	public Shader(
			InputStream vertexShaderInputStream,
			InputStream pixelShaderInputStream) {
		this.createProgram(vertexShaderInputStream, pixelShaderInputStream);
	}
	
	public Shader(
			InputStream vertexShaderInputStream,
			InputStream pixelShaderInputStream, Map<String, String> functions) {
		this.createProgram(vertexShaderInputStream, pixelShaderInputStream, functions);
	}
	
	public Shader(
			String vertexShaderSource,
			String pixelShaderSource) {
		this.createProgram(vertexShaderSource, pixelShaderSource);
	}
	
	public Shader(
			StringBuffer vertexShaderSource,
			StringBuffer pixelShaderSource, Map<String, String> functions) {
		this.createProgram(vertexShaderSource, pixelShaderSource, functions);
	}
	/**
	 * 使用Shader program
	 */
	public void glUseProgram() {
		GL20.glUseProgram(this.program);
	}
	
	/**
	 * 取消使用shader
	 */
	public static void glUnUseProgram() {
		GL20.glUseProgram(0);
	}

	/**
	 * 设置矩阵
	 */
	public void glModelViewProjectMatrix() {
		matrixHandler.glUniformMatrix();
	}
	
	public void checkGlError(String op) {
        int error;
        while ((error = GL11.glGetError()) != GL11.GL_NO_ERROR) {
        	logger.error("{}:glError{}", op, error);
            //throw new RuntimeException(op + ": glError " + error);
        }
    }
	
	/**
	 * 创建program
	 * @param vertexShaderInputStream
	 * @param pixelShaderInputStream
	 */
	protected void createProgram(
			InputStream vertexShaderInputStream,
			InputStream pixelShaderInputStream) {
		createProgram(Constants.loadStringSource(vertexShaderInputStream).toString(),
				Constants.loadStringSource(pixelShaderInputStream).toString());
	}
	
	/**
	 * 创建program
	 * @param vertexShaderInputStream
	 * @param pixelShaderInputStream
	 */
	protected void createProgram(
			InputStream vertexShaderInputStream,
			InputStream pixelShaderInputStream, 
			Map<String, String> functions) {
		createProgram(
				Constants.loadStringSource(vertexShaderInputStream),
				Constants.loadStringSource(pixelShaderInputStream), functions);
	}

	protected void createProgram(
			StringBuffer vertexShaderSource,
			StringBuffer pixelShaderSource, 
			Map<String, String> functions) {
		Constants.replace(vertexShaderSource, functions);
		Constants.replace(pixelShaderSource, functions);
		createProgram(vertexShaderSource.toString(), pixelShaderSource.toString());
	}
	
	/**
	 * 创建program
	 * @param vertexShaderSource
	 * @param pixelShaderSource
	 */
	protected void createProgram(
			String vertexShaderSource,
			String pixelShaderSource) {
		vertexShaderHandler = loadShader(GL20.GL_VERTEX_SHADER, 
				new StringBuffer()
					.append(getVersion()).append("\n")
					.append(getVertexHeader()).append("\n")
					.append(vertexShaderSource).toString());
		if (vertexShaderHandler == 0) {
			throw new NullPointerException("初始化Shader失败");
		}
		pixelShaderHandler = loadShader(GL20.GL_FRAGMENT_SHADER, 
				new StringBuffer()
					.append(getVersion()).append("\n")
					.append(getPixelHeader()).append("\n")
					.append(pixelShaderSource).toString());
		if (pixelShaderHandler == 0) {
			throw new NullPointerException("初始化Shader失败");
		}
		program = GL20.glCreateProgram();
		if (program != 0) {
			GL20.glAttachShader(program, vertexShaderHandler);
			GL20.glAttachShader(program, pixelShaderHandler);
			this.onBindAttribLocations(program);
			GL20.glLinkProgram(program);
			if (GL20.glGetProgrami(program, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) {
				logger.error(getLogInfo(program));
				deleteProgram();
				program = 0;
			}
		}
		GL20.glValidateProgram(program);
		if (GL20.glGetProgrami(program, GL20.GL_VALIDATE_STATUS) == GL11.GL_FALSE) {
			logger.error(getLogInfo(program));
			throw new NullPointerException("初始化Shader失败");
		}
		try {
			this.initializeHandler(this.program);
		} catch(Exception e) {
			logger.error("初始化错误----------", e);
			throw new NullPointerException("初始化Shader失败");
		}
		USING_SHADERS.add(this);
	}
	
	private static String getLogInfo(int obj) {
		return GL20.glGetProgramInfoLog(obj, GL20.glGetProgrami(obj, GL20.GL_INFO_LOG_LENGTH));
	}
	/**
	 * 删除program
	 */
	public void deleteProgram() {
		GL20.glDeleteShader(this.vertexShaderHandler);
		GL20.glDeleteShader(this.pixelShaderHandler);
		GL20.glDeleteProgram(program);
	}

	private int loadShader(int shaderType, String source) {
		int shader = GL20.glCreateShader(shaderType);
		if (shader != 0) {
			GL20.glShaderSource(shader, source);
			GL20.glCompileShader(shader);
			if (GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
				logger.error("无法编译shader " + shaderType + ":");
				logger.error(GL20.glGetShaderInfoLog(shader, GL20.glGetShaderi(shader, GL20.GL_INFO_LOG_LENGTH)));
				GL20.glDeleteShader(shader);
				shader = 0;
			}
		}
		return shader;
	}
	
	private void initializeHandler(int program) {
		matrixHandler = MatrixHandler.createMatrixHandler(program);
		//公共属性（系统属性读取）
		this.onInitializeHandler(program);
	}
	
	protected String getVertexHeader() {
		return MatrixHandler.getUnimformParameterString();
	}
	
	protected String getPixelHeader() {
		return "";
	}
	
	protected String getVersion() {
		return "#200";
	}
	
	protected abstract void onInitializeHandler(int program);
	
	protected abstract void onBindAttribLocations(int program);
	
	protected int glGetUniformLocation(String name) {
		 return GL20.glGetUniformLocation(program, name);
	}
	
	protected void bindAttribLocation(String name) {
		GL20.glBindAttribLocation(program, loacation ++, name);
	}
	
	public final int getProgram() {
		return program;
	}

	public void destroy() {
		ClearUpGLRubbishManager.push(this);
	}
	
	public static void destroyAllShader() {
		for(Shader shader:USING_SHADERS) {
			shader.destroy();
		}
		USING_SHADERS.clear();
	}

	@Override
	public void clearup() {
		this.deleteProgram();
	}
}
